/*
 * CommunicationModuleBridgeFromZMQ.cpp
 * This class provides methods to establish connection, sending IMessage and receiving IMessage
 *
 */

#include "CommunicationModuleBridgeFromZMQ.hpp"

namespace CommunicationModuleSpace {
void my_free (void *data, void *hint){};
/**
 * Constructor- initiate Class fields and socket connection
 */
CommunicationModuleBridgeFromZMQ::CommunicationModuleBridgeFromZMQ()
{
	context=NULL;
	socket=NULL;
	connectionPort=0;
	logger=NULL;
	isEndReceived = false;
	isClient = false;
	requestmsg = false;
}

CommunicationModuleBridgeFromZMQ::~CommunicationModuleBridgeFromZMQ ()
{
	( * socket ).close () ;
	delete(context);
	delete(socket);
	delete(logger);

}

bool CommunicationModuleBridgeFromZMQ::createConnection()
{
	try
	{
		if (isClient)
		{
			context = new context_t ( 1 ) ;
			socket = new socket_t ( * context , ZMQ_REQ ) ;
			//connect socket to host
			char buffer [ 50 ] ;
			sprintf ( buffer , "tcp://%s:%d" , connectionHost.c_str () , connectionPort ) ;
			( * socket ).connect ( buffer ) ;
			return true;
		}
		else
		{
			context = new context_t ( 1 ) ;
			socket = new socket_t ( * context , ZMQ_REP ) ;
			string bindstr = appendString("tcp://127.0.0.1:",int2str(connectionPort));
			(* socket).bind(bindstr.c_str());
			return true;
		}
	}
	catch (zmq::error_t& e)
	{
		logZMQException("createConnection", "error creating connection.", e );
		isReady = false;
		return false;
	}

}
/**
 * set the connection as client (used for Pass process)
 * port - the port connecting to satellite
 * host - the satellite simulator ip address
 */
bool CommunicationModuleBridgeFromZMQ::setTypeClient ( int port , string host )
{
	logger =  new Logger(CoreSettings::getInstance().getClientExcpLogFile());
	connectionPort = port ;
	connectionHost = host ;
	isClient = true;
	return createConnection();
}

/**
 * set the connection as server (used for Satellite simulator)
 * port - the port connecting on which hosting
 */
bool CommunicationModuleBridgeFromZMQ::setTypeServer ( int port  )
{
	logger = new Logger(CoreSettings::getInstance().getServerExcpLogFile());
	connectionPort = port ;
	isClient = false;
	return createConnection();
}
//@override
bool CommunicationModuleBridgeFromZMQ::transmitINITcommand(long timeout) throw (boost::thread_interrupted&)
{

	bool success = false;
	long maxTime = getNowLongTimeSeconds() + timeout;
	Message msg;
	msg.set_type(msg.INIT);
	Message msg2;

	while (!success)
	{
		success= sendAndWaitForReceiveWithTimeout(msg , &msg2, timeout);
		if ( success &&  msg2.type() != msg.ACK )
			success = false;
		boost::this_thread::interruption_point();
		timeout = maxTime - getNowLongTimeSeconds();
	}
	return success;
}

//@override
bool CommunicationModuleBridgeFromZMQ::transmitGOCMD(long timeout, PassLogger * receivedDatalogger) throw (boost::thread_interrupted&)
{
	isEndReceived = false;

	bool success = false;
	int i=1;
	long maxTime = getNowLongTimeSeconds() + timeout;
	Message msg;
	msg.set_type(msg.GO_CMD);
	Message msg2;
	success = sendAndWaitForReceiveWithTimeout(msg , &msg2, timeout);
	ProgressStatus status(ProgressStatusType::INF, "CommunicationModuleBridgeFromZMQ", "transmitGOCMD" , "receiving data from satellite" , true, true);
	receivedDatalogger->logInfo(status,true);

	while (success && !isEndReceived)
	{
		if (msg2.type() == msg2.END_CMD )
		{
			isEndReceived = true;
		}
		else if (msg2.type() == msg2.SAT_I_DATA)
		{
			if (CoreSettings::getInstance().isColorTextSupported())
			{
				ProgressStatus status1(ProgressStatusType::INF, "CommunicationModuleBridgeFromZMQ", "transmitGOCMD" , "received messages:" + int2str(i) , true, true);
				receivedDatalogger->logInfoCR(status1, LoggingColor::BLACK, i==1 ? true : false , false);
				i++;
			}
			else
			{
				ProgressStatus status1(ProgressStatusType::INF, "CommunicationModuleBridgeFromZMQ", "transmitGOCMD" , "received data from satellite: " + msg2.content() , false, true);
				receivedDatalogger->logInfo(status1);
			}

			timeout = maxTime - getNowLongTimeSeconds();
			success = sendAndWaitForReceiveWithTimeout(msg , &msg2, timeout);
		}
		else success = false;
		boost::this_thread::interruption_point();
	}
	ProgressStatus status2(ProgressStatusType::INF, "CommunicationModuleBridgeFromZMQ", "transmitGOCMD" , "Total received messages:" + int2str(--i) , true, true);
	receivedDatalogger->logInfo(status2,true);
	return success;
}

//@override
bool CommunicationModuleBridgeFromZMQ::sendCommandToSat(long timeout, string commandContent)
{
	Message msg;
	msg.set_type(msg.CMD_TO_SAT_I);
	msg.set_content(commandContent);
	Message msg2;
	bool success = false;

	success= sendAndWaitForReceiveWithTimeout(msg , &msg2, timeout);
	if ( success &&  msg2.type() != msg.ACK )
	{
		success = false;
		return success;
	}

	return success;
}

//@override
bool CommunicationModuleBridgeFromZMQ::sendENDCommand(long timeout) throw (boost::thread_interrupted&)
{
	Message msg;
	msg.set_type(msg.END_CMD);
	Message msg2;

	bool success = false;
	long maxTime = getNowLongTimeSeconds() + timeout;

	while (!success)
	{
		success= this->sendAndWaitForReceiveWithTimeout(msg, &msg2,timeout);
		boost::this_thread::interruption_point();
		timeout = maxTime - getNowLongTimeSeconds();
	}

	return success;
}


//@override
bool CommunicationModuleBridgeFromZMQ::sendAndWaitForReceive ( Message request , Message * received)
{
	bool success = send(request);
	if (success)
		return receive (received);
	return success;
}

//@override
bool CommunicationModuleBridgeFromZMQ::sendAndWaitForReceiveWithTimeout ( Message request ,Message * received ,  int timeoutsec )
{
	if (timeoutsec<0)
		timeoutsec = 0;
	setSocketTimeout(ZMQ_SNDTIMEO, timeoutsec);
	setSocketTimeout(ZMQ_RCVTIMEO, timeoutsec);
	bool success = sendAndWaitForReceive ( request , received);
	setSocketTimeout(ZMQ_SNDTIMEO, 0);
	setSocketTimeout(ZMQ_RCVTIMEO, 0);
	return success;
}
//@override
bool CommunicationModuleBridgeFromZMQ::sendWithTimeout(Message message ,  int timeoutsec )
{
	if (timeoutsec<1)
		return send(message);

	setSocketTimeout(ZMQ_SNDTIMEO, timeoutsec);
	bool success = send(message);
	setSocketTimeout(ZMQ_SNDTIMEO, 0);
	return success;
}
//@override
bool CommunicationModuleBridgeFromZMQ::receiveWithTimeout (Message * received , int timeoutsec )
{
	if (timeoutsec<1)
		return receive (received);
	setSocketTimeout(ZMQ_RCVTIMEO, timeoutsec);
	bool success =  receive (received);
	setSocketTimeout(ZMQ_RCVTIMEO, 0);
	return success;
}

//@override
bool CommunicationModuleBridgeFromZMQ::receive (Message * received)
{
	//create reply obj and receiving it
	message_t msg ;
	bool success;

	try
	{
		success = ( * socket ).recv ( & msg ) ;
	}
	catch (zmq::error_t& e)
	{
		logZMQException("receive","exception receiving message.", e );
		( * socket ).close();
		delete(socket);
		isReadyForPass();
	}

	if (success)
	{
		//fill received message
		success = received->ParseFromArray(msg.data(), msg.size());
	}
	//return success
	return success ;
}
//@override
bool CommunicationModuleBridgeFromZMQ::send(Message message)
{
	message_t * msg = createMessage_t(message);
	bool success = send ( msg  ) ;
	return success;
}



message_t * CommunicationModuleBridgeFromZMQ::createMessage_t(Message message)
{
	string sirRequest = message.SerializeAsString () ;
	try
	{
		requestmsg = new message_t ( sirRequest.size ()) ;
		memcpy ( ( void * ) ( * requestmsg ).data () , ( void * ) sirRequest.c_str () , sirRequest.size ()) ;
	}
	catch (zmq::error_t& e)
	{
		logZMQException("createMessage_t", "error initiating message_t object.", e );
	}

	return requestmsg;
}

bool CommunicationModuleBridgeFromZMQ::send ( message_t *request )
{
	//send the message
	bool success;
	try
	{
		success = ( * socket ).send ( * request ) ;
	}
	catch (zmq::error_t& e)
	{
		logZMQException("send","exception sending message.", e );
		( * socket ).close();
		delete(socket);
		isReadyForPass();
	}
	return success;
}

void CommunicationModuleBridgeFromZMQ::setSocketTimeout(int opt, int timeoutsec)
{
	int timeoutmilis;
	if (timeoutsec < 0)
		return;
	else timeoutmilis = timeoutsec * 1000;

	try
	{
		(*socket).setsockopt(opt,&timeoutmilis,sizeof(timeoutmilis));
	}
	catch (zmq::error_t& e)
	{
		logZMQException("setSocketTimeout","exception setting socket timeout.", e );
	}
}

//@override
bool CommunicationModuleBridgeFromZMQ::isReadyForPass()
{
	if (socket != NULL && ( * socket ).connected())
		return true;

	return createConnection();
}

void CommunicationModuleBridgeFromZMQ::logZMQException(string function,string content, zmq::error_t& e )
{


	content.append(" (exception info: ");
	content.append(int2str(e.num()));
	content.append("- ");
	content.append(e.what());
	content.append(")");
	ProgressStatus status(ProgressStatusType::ERR, "CommunicationModuleBridgeFromZMQ", function , content, true, true);
	logger->logError(status);
}

}

